Skip to content

Add server failover rotation for classworkscloud provider#36

Merged
Sunwuyuan merged 4 commits intomainfrom
copilot/add-server-polling-for-classworks
Jan 19, 2026
Merged

Add server failover rotation for classworkscloud provider#36
Sunwuyuan merged 4 commits intomainfrom
copilot/add-server-polling-for-classworks

Conversation

Copy link
Contributor

Copilot AI commented Jan 19, 2026

Implements automatic failover across multiple server endpoints for the classworkscloud provider to handle server outages. Servers are tried in priority order (kv-service.houlang.cloudkv-service.wuyuan.dev) on every request, allowing immediate recovery when primary server returns.

Changes

New rotation utility (src/utils/serverRotation.js)

  • tryWithRotation(): Executes operations with automatic server fallback
  • getEffectiveServerUrl(): Returns primary server URL for initial connections
  • isRotationEnabled(): Guards rotation logic to classworkscloud provider only

Provider layer updates

  • kvServerProvider.js: All API methods (loadData, saveData, loadKeys, loadNamespaceInfo, updateNamespaceInfo) use rotation when enabled
  • api.js, dataProvider.js: Apply rotation to namespace operations and cloud URL generation
  • socketClient.js: Uses effective server URL for Socket.IO (rotation not applicable at transport layer)

Enhanced connection diagnostics

  • DataProviderSettingsCard.vue: Connection test button now displays detailed attempt log showing which servers were tried and their status

Example

// Rotation automatically tries servers in sequence
await kvServerProvider.loadData('exam_configs');
// Console: "Server https://kv-service.houlang.cloud failed: ECONNREFUSED"
// Console: "Server https://kv-service.wuyuan.dev succeeded"

// User-facing connection test shows:
// "服务器连接正常 延迟234ms
//  依次尝试的服务器:
//  1. https://kv-service.houlang.cloud (失败)
//  2. https://kv-service.wuyuan.dev (成功)"

Backward compatibility: kv-local and kv-server providers unaffected—rotation only activates for classworkscloud.

Original prompt

对于provider类型为classworks云服务的情况,使用轮询服务器地址,避免某个服务器故障,地址从上到下为kv-service.houlang.cloud,kv-service.wuyuan.dev,不要影响现有代码逻辑,不要改变现有provider的调用方式,注意服务器随时都可能恢复,所以始终从第一个开始调用,坏了再换下一个,都不行就原先的报错流程,并输出调用了哪些服务器。检查服务器连接的按钮点击后显示依次调用了哪些服务器。注意socketio的轮询问题,不要执行npm lint


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com>
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 19, 2026

Deploying classworks with  Cloudflare Pages  Cloudflare Pages

Latest commit: a997e35
Status: ✅  Deploy successful!
Preview URL: https://830afba1.classworks.pages.dev
Branch Preview URL: https://copilot-add-server-polling-f.classworks.pages.dev

View logs

Copilot AI and others added 2 commits January 19, 2026 11:57
…O limitation

Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com>
Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add polling mechanism for classworks cloud service Add server failover rotation for classworkscloud provider Jan 19, 2026
Copilot AI requested a review from Sunwuyuan January 19, 2026 12:01
@Sunwuyuan Sunwuyuan marked this pull request as ready for review January 19, 2026 12:08
Copilot AI review requested due to automatic review settings January 19, 2026 12:08
@Sunwuyuan Sunwuyuan merged commit 7ce2deb into main Jan 19, 2026
3 checks passed
@Sunwuyuan
Copy link
Member

@copilot getEffectiveServerUrl似乎会导致资源浪费,改成直接使用第一个url访问资源,出网络问题了再试下一个

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements automatic server failover for the classworkscloud provider to handle outages by trying servers in priority order (kv-service.houlang.cloud → kv-service.wuyuan.dev). The implementation adds a new rotation utility and integrates it across the API layer while maintaining backward compatibility with other providers.

Changes:

  • New server rotation utility with fallback mechanism for classworkscloud provider
  • Integration of rotation logic into all data provider methods (loadData, saveData, loadKeys, loadNamespaceInfo, updateNamespaceInfo)
  • Enhanced connection diagnostics showing attempted servers and their status

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/utils/serverRotation.js New utility implementing tryWithRotation, getEffectiveServerUrl, and isRotationEnabled for server failover
src/utils/providers/kvServerProvider.js All five API methods updated to use rotation when classworkscloud provider is active
src/utils/api.js getNamespaceInfo method updated to support rotation
src/utils/dataProvider.js getKeyCloudUrl updated to use effective server URL for classworkscloud
src/utils/socketClient.js Socket connection uses effective server URL (primary server only, no mid-connection rotation)
src/components/settings/cards/DataProviderSettingsCard.vue Connection test enhanced to display attempt log with server status
Comments suppressed due to low confidence (1)

src/utils/providers/kvServerProvider.js:50

  • The rotation logic pattern is duplicated across all five methods in this file (loadNamespaceInfo, updateNamespaceInfo, loadData, saveData, loadKeys). This creates a maintenance burden where changes to the rotation behavior must be applied in five separate locations. Consider extracting this into a helper function that wraps the server-specific logic.
  async loadNamespaceInfo() {
    try {
      // Use rotation for classworkscloud provider
      if (isRotationEnabled()) {
        return await tryWithRotation(async (serverUrl) => {
          const res = await axios.get(`${serverUrl}/kv/_info`, {
            headers: getHeaders(),
          });
          return formatResponse(res.data);
        });
      }

      // Standard single-server mode
      const serverUrl = getSetting("server.domain");
      const res = await axios.get(`${serverUrl}/kv/_info`, {
        headers: getHeaders(),
      });

      // 直接返回新格式 API 数据,包含 device 和 account 信息
      return formatResponse(res.data);
    } catch (error) {
      console.error("获取命名空间信息失败:", error);
      return formatError(
        error.response?.data?.message || "获取命名空间信息失败",
        "NAMESPACE_ERROR"
      );
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +82 to +88
if (isRotationEnabled()) {
return await tryWithRotation(async (serverUrl) => {
const res = await axios.get(`${serverUrl}/kv/${key}`, {
headers: getHeaders(),
});
return formatResponse(res.data);
});
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same error handling inconsistency as in other methods: when tryWithRotation fails, the error thrown from serverRotation.js may not have the error.response structure expected by the catch block at lines 97-106, particularly when checking error.response?.status and error.response?.data?.message.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +118
if (isRotationEnabled()) {
return await tryWithRotation(async (serverUrl) => {
await axios.post(`${serverUrl}/kv/${key}`, data, {
headers: getHeaders(),
});
return formatResponse(true);
});
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same error handling inconsistency as in other methods: when tryWithRotation fails, the error thrown from serverRotation.js may not have the error.response structure expected by the catch block at lines 126-132.

Copilot uses AI. Check for mistakes.
Comment on lines +175 to +181
if (isRotationEnabled()) {
return await tryWithRotation(async (serverUrl) => {
const res = await axios.get(`${serverUrl}/kv/_keys?${params}`, {
headers: getHeaders(),
});
return formatResponse(res.data);
});
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same error handling inconsistency as in other methods: when tryWithRotation fails, the error thrown from serverRotation.js may not have the error.response structure expected by the catch block at lines 190-205, particularly when checking error.response?.status for 404, 403, and 401 errors.

Copilot uses AI. Check for mistakes.
// All servers failed
console.error("All servers failed. Tried:", triedServers);
const error = lastError || new Error("All servers failed");
error.triedServers = triedServers;
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When all servers fail, the error thrown here preserves the lastError from the final server attempt, which is an axios error with error.response structure. However, if the operation function throws a non-axios error (e.g., a validation error or custom error), the error handling in calling code that expects error.response will break. Consider documenting that the operation function should throw axios errors, or wrapping errors consistently to ensure they have the expected structure for downstream error handlers.

Suggested change
error.triedServers = triedServers;
error.triedServers = triedServers;
// Ensure downstream handlers that expect an axios-style error.response do not break
if (!error.response) {
error.response = {
data: {
message: error.message || "All servers failed",
triedServers,
},
};
}

Copilot uses AI. Check for mistakes.

import {io} from 'socket.io-client';
import {getSetting} from '@/utils/settings';
import {getEffectiveServerUrl, isRotationEnabled, tryWithRotation} from '@/utils/serverRotation';
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tryWithRotation import is unused in this file. Only getEffectiveServerUrl and isRotationEnabled are actually used. Remove the unused import to keep the code clean.

Suggested change
import {getEffectiveServerUrl, isRotationEnabled, tryWithRotation} from '@/utils/serverRotation';
import {getEffectiveServerUrl, isRotationEnabled} from '@/utils/serverRotation';

Copilot uses AI. Check for mistakes.

if (triedServers.length > 0) {
const serverList = triedServers.map((s, i) =>
`${i + 1}. ${s.url} (失败${s.error ? `: ${s.error}` : ""})`
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message formatting has inconsistent spacing. Line 206 shows "失败${s.error ? : ${s.error} : ""}" which will produce output like "失败: error message" (with Chinese characters followed by a colon and space). However, in Chinese text formatting, it's more conventional to use Chinese punctuation (如:"失败:error message") or omit the space. Consider using either "失败: ${s.error}" with proper Chinese formatting or ensuring consistent spacing throughout the user-facing messages.

Suggested change
`${i + 1}. ${s.url} (失败${s.error ? `: ${s.error}` : ""})`
`${i + 1}. ${s.url} (失败${s.error ? `${s.error}` : ""})`

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +33
if (isRotationEnabled()) {
return await tryWithRotation(async (serverUrl) => {
const res = await axios.get(`${serverUrl}/kv/_info`, {
headers: getHeaders(),
});
return formatResponse(res.data);
});
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling for rotation failures is inconsistent with the non-rotation path. When tryWithRotation fails, the error object will be thrown from serverRotation.js line 80 and caught here. However, this error may not have the same structure as axios errors (e.g., error.response), which the catch block at lines 44-49 expects when checking error.response?.data?.message. This could result in less informative error messages when all servers fail during rotation. Consider normalizing the error structure from tryWithRotation or adjusting the error handling to work with both error types.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +62
if (isRotationEnabled()) {
return await tryWithRotation(async (serverUrl) => {
const res = await axios.put(`${serverUrl}/kv/_info`, data, {
headers: getHeaders(),
});
return res;
});
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same error handling inconsistency as in loadNamespaceInfo: when tryWithRotation fails, the error thrown from serverRotation.js may not have the error.response structure expected by the catch block at lines 71-76. This affects the quality of error messages presented to users when all servers fail.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +45
if (isRotationEnabled()) {
const response = await tryWithRotation(async (serverUrl) => {
return await axios.get(`${serverUrl}/kv/_info`, {
headers: getHeaders(),
});
});
return response.data;
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent error handling: when tryWithRotation fails, it throws an error that may not have the error.response structure expected by line 55 when checking error.response?.data?.message. This could result in less informative error messages when all servers fail during rotation.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +41
// For classworkscloud, create socket with the first server in rotation
// Note: Socket.IO's built-in reconnection will retry the same server URL.
// Server rotation is handled at the HTTP request level, not Socket.IO level.
// If the Socket.IO server goes down, the connection will fail until the server recovers.
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Socket.IO connections require a stable server URL and cannot benefit from HTTP-level rotation during the WebSocket connection lifecycle. The comment correctly notes that Socket.IO's built-in reconnection will retry the same server URL. However, this creates a potential availability issue: if the primary server (kv-service.houlang.cloud) fails after the socket is established, the application will lose real-time connectivity until that specific server recovers, even though the secondary server might be available. Consider implementing a mechanism to detect socket connection failures and recreate the socket with the next server in rotation, or document this limitation more prominently for users.

Suggested change
// For classworkscloud, create socket with the first server in rotation
// Note: Socket.IO's built-in reconnection will retry the same server URL.
// Server rotation is handled at the HTTP request level, not Socket.IO level.
// If the Socket.IO server goes down, the connection will fail until the server recovers.
// For classworkscloud, create the Socket.IO connection to a single effective server URL.
// IMPORTANT: Socket.IO's built-in reconnection will retry only this same server URL.
// Server rotation is applied when selecting the URL, but not during the WebSocket lifecycle.
// If this specific Socket.IO server becomes unavailable after connection, real-time updates
// from this client will be lost until that server recovers or the application explicitly
// tears down and recreates the socket (for example, after a full page reload or via
// custom app logic that re-calls getSocket() with a different server URL).

Copilot uses AI. Check for mistakes.
@wuyuancat
Copy link
Collaborator

此拉取请求已在 Kernyr 上被提及。那里可能有相关详细信息:

http://kernyr.wuyuan.dev/t/topic/175/1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants